/*
This file is part of La Linea.

La Linea is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

La Linea is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with La Linea.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "DisplayWidget.h"

#include "Font.h"
#include "FontManager.h"

using namespace std;

DisplayWidget::DisplayWidget(Animation* anim, QWidget *parent) : 
QGLWidget(QGLFormat(QGL::SampleBuffers), parent), 
_displayOptions(DRAW_CONTROLPOINTS | DRAW_ORIGINPOINTS | DRAW_BORDERLINE | DRAW_GHOST | DRAW_GRID | DRAW_OSD | DRAW_DEBUGSTENCIL | DRAW_BOUNDINGBOX),
_editMode(EDIT_POINTS)
{
	_screenWidth = width();
	_screenHeight = height();
	_screenFormat = (GLfloat)_screenWidth/_screenHeight;
	
	_translateScene = Vector2f((GLfloat)0,(GLfloat)0);
	_mouseLastPos = Vector2f((GLfloat)0,(GLfloat)0);
	_zoom = 1.0;
	
	_isPlaying = false;
	_dragAndDrop = false;
	
	_currentAnimation = anim;
	_selectedNode = 0;
	
	CacheManager::getInstance()->init(); // Initialize cache manager
	
	_timer = new QTimer(this);
    connect(_timer, SIGNAL(timeout()), this, SLOT(timeOutSlot()));
    _timer->start(1000/60);
	
	_FPStime = 0;
	_FPScount = 0;
	_FPSlastValue = 0;
	_timerFPS.start();
}

void DisplayWidget::initializeGL(){
	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
	  fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
	  exit(1);
	}
	
	// Alpha canal
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
	// Set the background color
	glClearColor((GLclampf)0.0,(GLclampf)0.39,(GLclampf)0.14,(GLclampf)1.0);
	
	_displayFont = FontManager::getInstance()->getFont("arial.ttf", 18);
}

void DisplayWidget::resizeGL(int width, int height){
	glViewport(0,0,(GLsizei) width, (GLsizei) height);
	glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    gluOrtho2D(0,((GLfloat)width/height),0,1);
	glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
	
	_screenWidth = width;
	_screenHeight = height;
	_screenFormat = (GLfloat)width/height;
	_translateScene = Vector2f(_screenFormat/2,(GLfloat)0.10);
}

void DisplayWidget::paintGL(){
	// Clear the screen
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	// Translate and zoom in the scene
	glTranslatef(_translateScene.x, _translateScene.y, (GLfloat)0.0);
	glScalef(_zoom, _zoom, 0);
	
	// FPS
	qint64 elapsedTime = _timerFPS.elapsed();
	_FPScount++;
	_FPStime+= (int)elapsedTime;
	if(_FPStime >= 1000){
		_FPSlastValue = _FPScount;
		_FPStime = 0;
		_FPScount = 0;
	}	
	_timerFPS.restart();
	
	// Display the grid if it is activated
	if((_displayOptions & DRAW_GRID) == DRAW_GRID) displayGrid();
	
	if(_currentAnimation){
		// Update the animation
		if(_isPlaying){
			_currentAnimation->update((GLfloat)elapsedTime/1000);
		}
		
		if(_editMode == EDIT_POINTS) _currentAnimation->draw(1.0, 1.0, 1.0, 1.0, _displayOptions);	
		else if(_editMode == EDIT_STENCIL) _currentAnimation->draw(1.0, 1.0, 1.0, 1.0, _displayOptions & (~DRAW_CONTROLPOINTS));
	}

	if((_displayOptions & DRAW_OSD) == DRAW_OSD) displayOSD();
	
	// Display the selected node
	if(_selectedNode){
		glPointSize(7.0);
		glColor3f(1.0, 0.0, 0.0);
		glBegin(GL_POINTS);
			glVertex2f(_selectedNode->_pos.x, _selectedNode->_pos.y);
		glEnd();
	}
	
	if(_editMode == EDIT_STENCIL){
		vector<Node*>::iterator it = _selectedStencilNodes.begin();		
		glPointSize(9.0);
		glColor3f(0.0, 0.0, 0.8);
		glBegin(GL_POINTS);
		for(; it != _selectedStencilNodes.end(); ++it){
			glVertex2f((*it)->_pos.x, (*it)->_pos.y);
		}
		glEnd();
	}
}

void DisplayWidget::keyPressEvent( QKeyEvent *keyEvent ){
}

void DisplayWidget::wheelEvent(QWheelEvent *event){ // Mouse wheel : (Un)zoom / Change the size of the selected node
	int delta = event->delta();
	if(delta > 0){
		if(_selectedNode && _editMode == EDIT_POINTS){
			_selectedNode->_size += 0.005;
			emit nodeUpdated();
			CacheManager::getInstance()->getFrameCache()->invalidate(_currentAnimation->getCurrentFrame());
		}
		else
			_zoom+=(GLfloat)0.03;
	}
	else{
		if(_selectedNode && _editMode == EDIT_POINTS){
			_selectedNode->_size = (_selectedNode->_size > 0.005)?_selectedNode->_size-0.005:0.0;			
			emit nodeUpdated();
			CacheManager::getInstance()->getFrameCache()->invalidate(_currentAnimation->getCurrentFrame());
		}
		else
		_zoom = (_zoom > 0.03)?_zoom - (GLfloat)0.03:(GLfloat)0.03;
	}
}

void DisplayWidget::mouseMoveEvent(QMouseEvent *event){
	// Mouse position in the scene
	Vector2f posSouris = Vector2f((((GLfloat)event->x()*_screenFormat)/_screenWidth), 1-(GLfloat)event->y()/_screenHeight);
	posSouris.x -= _translateScene.x;
	posSouris.y -= _translateScene.y;
	posSouris.x /= _zoom;
	posSouris.y /= _zoom;
	
	// Mouse displacement since the last update
	Vector2f posRel = Vector2f((GLfloat)(event->x()),(GLfloat)(event->y()))-_translateScene;
	posRel = posRel - _mouseLastPos;
	
	// If a node is selected, move it ! (and disable the cache)
	if(_dragAndDrop && _selectedNode){
		if(_editMode == EDIT_POINTS){
			_selectedNode->_pos.x = posSouris.x;
			_selectedNode->_pos.y = posSouris.y;		
			emit nodeUpdated();
		}
		_displayOptions = _displayOptions | DRAW_NOCACHE;
	}
	// If no node is selected and we're in drag & drop mode, we move the scene
	else if (_dragAndDrop){
		_translateScene.x = _translateScene.x + ((GLfloat)(posRel.x)*_screenFormat/_screenWidth);
		_translateScene.y = _translateScene.y - ((GLfloat)(posRel.y)/(GLfloat)_screenHeight);
	}

	_mouseLastPos = Vector2f((GLfloat)(event->x()),(GLfloat)(event->y()))-_translateScene;
}

void DisplayWidget::mousePressEvent(QMouseEvent * event){
	// Mouse position	
	Vector2f posSouris = Vector2f((((GLfloat)event->x()*_screenFormat)/_screenWidth), 1-(GLfloat)event->y()/_screenHeight);
	posSouris.x -= _translateScene.x;
	posSouris.y -= _translateScene.y;
	posSouris.x /= _zoom;
	posSouris.y /= _zoom;
	
	if(event->button() == Qt::LeftButton){ // Left button : (Un)Select a node
		bool isNearPoint = false;		
		_selectedNode = 0;
		Node* nearestPoint = 0;		
		
		if(_editMode == EDIT_POINTS) nearestPoint = _currentAnimation->nearestNode(posSouris);
		else if (_editMode == EDIT_STENCIL) nearestPoint = _currentAnimation->nearestOriginNode(posSouris);
		
		// If there's a point for which the distance to the cursor is less than 0.02, we select
		// it and go to drag & drop mode
		if(nearestPoint && !_dragAndDrop && (posSouris-nearestPoint->_pos).length() < 0.02){
			isNearPoint = true;
			_dragAndDrop = true;
			if(_editMode == EDIT_STENCIL && nearestPoint != _selectedNode) _selectedStencilNodes.push_back(nearestPoint);
			_selectedNode = nearestPoint;	
		}		
		else{
			if(_editMode == EDIT_STENCIL) _selectedStencilNodes.clear();
		}
		
		emit nodeSelected(_selectedNode);
	}
	else if (event->button() == Qt::RightButton){ // Right button : Create a new node
		if(_editMode == EDIT_POINTS)	_currentAnimation->addPoint(posSouris, _selectedNode);
	}
	else if (event->button() == Qt::MidButton){ // Mid button : drag & drop the scene
		if(!_dragAndDrop){
			_dragAndDrop = true;
			_selectedNode = 0;
			emit nodeSelected(_selectedNode);
		}	
	}
	
	_mouseLastPos = Vector2f((GLfloat)(event->x()),(GLfloat)(event->y()))-_translateScene;
}

void DisplayWidget::mouseReleaseEvent(QMouseEvent * event){ // Mouse release : leave drag & drop mode and re-enable the cache
	if(_dragAndDrop){
		if(_selectedNode) CacheManager::getInstance()->getFrameCache()->invalidate(_currentAnimation->getCurrentFrame());
		_dragAndDrop = false;
		_displayOptions = _displayOptions & ~DRAW_NOCACHE;
	}
}

void DisplayWidget::timeOutSlot(){
	updateGL();
}

void DisplayWidget::displayGrid(){
	glBegin(GL_LINES);

	for(int i=-80; i<80; i++){
			if(i==0)
				glColor3f(1.0, 1.0, 1.0);
			else
				glColor4f(0.5, 0.5, 0.5, 0.5);

			glVertex3f(-4,(GLfloat)i/20,1);
			glVertex3f(4,(GLfloat)i/20,1);
	}

	for(int i=-80; i<80; i++){
			if(i==0)
				glColor3f(1.0, 1.0, 1.0);
			else
				glColor4f(0.5, 0.5, 0.5, 0.5);

			glVertex3f((GLfloat)i/20,-4,1);
			glVertex3f((GLfloat)i/20,4,1);
	}
	
	glEnd();
}

void DisplayWidget::displayOSD(){
	ostringstream oss;
	oss << "FPS : " << _FPSlastValue << endl;
	oss << "Frame : "<<_currentAnimation->getCurrentFrameNumber()+1<<"/"<<_currentAnimation->getFrameNumber()<<endl;
	oss << "Frame duration : "<<_currentAnimation->getFrameDuration()<<" seconds"<<endl;
	glColor3f(1.0f,1.0f,1.0f);
	Vector2f v = Vector2f(10.0,(GLfloat)_screenHeight-30);
	FontManager::write(*_displayFont, oss.str(), v);
}

void DisplayWidget::setAnimation(Animation* anim){
	_currentAnimation = anim;
}

void DisplayWidget::tooglePoints(){
	_displayOptions = _displayOptions ^ DRAW_CONTROLPOINTS;
	_displayOptions = _displayOptions ^ DRAW_ORIGINPOINTS;
}

void DisplayWidget::toogleCurvePoints(){
	_displayOptions = _displayOptions ^ DRAW_CURVEPOINTS;
}

void DisplayWidget::toogleCurves(){
	_displayOptions = _displayOptions ^ DRAW_CURVELINE;
}

void DisplayWidget::toogleBorder(){
	_displayOptions = _displayOptions ^ DRAW_BORDERPOINTS;
}

void DisplayWidget::toogleFinal(){
	_displayOptions = _displayOptions ^ DRAW_BORDERLINE;
}

void DisplayWidget::toogleBoundingBox(){
	_displayOptions = _displayOptions ^ DRAW_BOUNDINGBOX;
}

void DisplayWidget::toogleGhost(){
	_displayOptions = _displayOptions ^ DRAW_GHOST;
}

void DisplayWidget::toogleGrid(){
	_displayOptions = _displayOptions ^ DRAW_GRID;
}

void DisplayWidget::tooglePlay(){
	if(_isPlaying) _isPlaying = false;
	else {
		if(_selectedNode) _selectedNode = 0;
		_isPlaying = true;
	}
}

void DisplayWidget::insertFrameBegin(){
	_currentAnimation->addFrame(new Frame(), 0);
}

void DisplayWidget::insertFrameEnd(){
	_currentAnimation->addFrame(new Frame());
}

void DisplayWidget::insertFrameBefore(){
	_currentAnimation->addFrame(new Frame(), _currentAnimation->getCurrentFrameNumber());
}

void DisplayWidget::insertFrameAfter(){
	_currentAnimation->addFrame(new Frame(), _currentAnimation->getCurrentFrameNumber()+1);
}

void DisplayWidget::duplicateFrameBegin(){
	_currentAnimation->addFrame(_currentAnimation->getCurrentFrame()->copy(), 0);
}

void DisplayWidget::duplicateFrameEnd(){
	_currentAnimation->addFrame(_currentAnimation->getCurrentFrame()->copy());
}

void DisplayWidget::duplicateFrameBefore(){
	_currentAnimation->addFrame(_currentAnimation->getCurrentFrame()->copy(), _currentAnimation->getCurrentFrameNumber());
}

void DisplayWidget::duplicateFrameAfter(){
	_currentAnimation->addFrame(_currentAnimation->getCurrentFrame()->copy(), _currentAnimation->getCurrentFrameNumber()+1);
}

void DisplayWidget::clearFrame(){
	_selectedNode = 0;
	_selectedStencilNodes.clear();
	
	emit nodeSelected(_selectedNode);
	_currentAnimation->replaceCurrentFrame(new Frame());
}

void DisplayWidget::removeFrame(){
	_selectedNode = 0;
	_selectedStencilNodes.clear();
	
	emit nodeSelected(_selectedNode);
	_currentAnimation->removeFrame();
}

void DisplayWidget::previousFrame(){
	_selectedNode = 0;
	_selectedStencilNodes.clear();
	
	emit nodeSelected(_selectedNode);
	_currentAnimation->previousFrame();
}

void DisplayWidget::nextFrame(){
	_selectedNode = 0;
	_selectedStencilNodes.clear();
	
	emit nodeSelected(_selectedNode);
	_currentAnimation->nextFrame();
}

void DisplayWidget::increaseFrameDuration(){
	_currentAnimation->setFrameDuration(_currentAnimation->getFrameDuration()+0.01f);
}

void DisplayWidget::decreaseFrameDuration(){
	if(_currentAnimation->getFrameDuration() > 0.02)
		_currentAnimation->setFrameDuration(_currentAnimation->getFrameDuration()-0.01f);
}

void DisplayWidget::unselect(){
	_selectedNode = 0;
	_selectedStencilNodes.clear();
	
	emit nodeSelected(_selectedNode);
}

void DisplayWidget::deleteSelectedNode(){
	if(_selectedNode){
		_currentAnimation->getCurrentFrame()->deleteNode(_selectedNode);
		_selectedNode = 0;
		_selectedStencilNodes.clear();
		
		emit nodeSelected(_selectedNode);
		CacheManager::getInstance()->getFrameCache()->invalidate(_currentAnimation->getCurrentFrame());
	}
}

void DisplayWidget::setEditPointsMode(){
	_editMode = EDIT_POINTS;
	_selectedNode = 0;
	_selectedStencilNodes.clear();
	
	emit nodeSelected(_selectedNode);
}

void DisplayWidget::setEditStencilMode(){
	_editMode = EDIT_STENCIL;
	_selectedNode = 0;
	_selectedStencilNodes.clear();
	
	emit nodeSelected(_selectedNode);
}

void DisplayWidget::setBackgroundColor(GLfloat r, GLfloat g, GLfloat b){
	glClearColor((GLclampf)r,(GLclampf)g,(GLclampf)b,(GLclampf)1.0);
}

void DisplayWidget::toogleOSD(){
	_displayOptions = _displayOptions ^ DRAW_OSD;
}

FrameStencil* DisplayWidget::createStencil(){
	FrameStencil *stencil = new FrameStencil();
	Node* lastNodeOriginal = *(_selectedStencilNodes.begin());
	StencilNode* lastNode = new StencilNode((*(_selectedStencilNodes.begin()))->_pos);
	
	vector<Node*>::iterator it = _selectedStencilNodes.begin()+1;
	for(; it != _selectedStencilNodes.end(); ++it){
		StencilCurve* curve = new StencilCurve();
		
		StencilNode* currentNode = new StencilNode((*it)->_pos);
		
		Node* controlNode = _currentAnimation->getCurrentFrame()->getControlNode(lastNodeOriginal, (*it));
		StencilNode* controlNodeStencil = 0;
		if(controlNode) controlNodeStencil = new StencilNode(controlNode->_pos);
		else controlNodeStencil = new StencilNode((currentNode->_pos+lastNode->_pos)/2);
		
		curve->_origins[0] = lastNode;
		curve->_origins[1] = currentNode;
		curve->_controlpoint = controlNodeStencil;
		
		stencil->addCurve(curve);
		
		lastNode = currentNode;
		lastNodeOriginal = (*it);
	}
	
	return stencil;
}

void DisplayWidget::confirmPosStencil(){
	if(_editMode != EDIT_STENCIL) return;
	if(_selectedStencilNodes.size() < 3) return;
	
	_currentAnimation->addPosStencil(createStencil());
	
	_selectedStencilNodes.clear();
}

void DisplayWidget::confirmNegStencil(){
	if(_editMode != EDIT_STENCIL) return;
	if(_selectedStencilNodes.size() < 3) return;
	
	_currentAnimation->addNegStencil(createStencil());
	
	_selectedStencilNodes.clear();
}

void DisplayWidget::toogleDebugStencil(){
	_displayOptions = _displayOptions ^ DRAW_DEBUGSTENCIL;	
}

void DisplayWidget::deleteFrameStencils(){
	_currentAnimation->getCurrentFrame()->deleteStencils();
}